package pl.allegro.tech.hermes.consumers.consumer.rate.calculator;

import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.assertj.core.data.Offset;
import pl.allegro.tech.hermes.consumers.consumer.rate.SendCounters;
import pl.allegro.tech.hermes.consumers.consumer.rate.calculator.OutputRateCalculator.Mode;

public class OutputRateCalculationScenario {

  private final List<OutputRateCalculationResult> results = new ArrayList<>();

  private OutputRateCalculationResult previous;

  private final OutputRateCalculator calculator;

  private final SendCounters counters = new SendCounters(Clock.systemDefaultZone());

  public OutputRateCalculationScenario(OutputRateCalculator calculator) {
    this.calculator = calculator;
  }

  public OutputRateCalculationScenario start(double initialRate) {
    previous = calculator.recalculateRate(counters, OutputRateCalculator.Mode.NORMAL, initialRate);
    results.add(previous);
    return this;
  }

  public OutputRateCalculationScenario nextInteration(int successes, int failures) {
    incrementSuccesses(successes);
    incrementFailures(failures);
    previous = calculator.recalculateRate(counters, previous.mode(), previous.rate());
    results.add(previous);
    counters.reset();
    return this;
  }

  private void incrementSuccesses(int times) {
    for (int i = 0; i < times; ++i) {
      counters.incrementSuccesses();
    }
  }

  private void incrementFailures(int times) {
    for (int i = 0; i < times; ++i) {
      counters.incrementFailures();
    }
  }

  public OutputRateCalculationScenario nextSuccessIteration() {
    return nextInteration(1, 0);
  }

  public OutputRateCalculationScenario nextFailureIteration() {
    return nextInteration(0, 1);
  }

  public OutputRateCalculationScenario fastForwardWithSuccesses(int iterations) {
    for (int i = 0; i < iterations; ++i) {
      nextSuccessIteration();
    }
    return this;
  }

  public OutputRateCalculationScenario fastForwardWithFailures(int iterations) {
    for (int i = 0; i < iterations; ++i) {
      nextFailureIteration();
    }
    return this;
  }

  public void verifyModes(int offset, Mode... modes) {
    for (int i = offset; i < modes.length; ++i) {
      Assertions.assertThat(results.get(i).mode()).isEqualTo(modes[i]);
    }
  }

  public void verifyRates(int offset, double... rates) {
    for (int i = offset; i < rates.length; ++i) {
      Assertions.assertThat(results.get(i).rate()).isEqualTo(rates[i]);
    }
  }

  public OutputRateCalculationScenario verifyIntermediateResult(
      int iteration, double rate, Mode mode) {
    Assertions.assertThat(results.get(iteration).rate()).isEqualTo(rate, Offset.offset(0.1));
    Assertions.assertThat(results.get(iteration).mode()).isEqualTo(mode);
    return this;
  }

  public OutputRateCalculationScenario verifyFinalResult(double rate, Mode mode) {

    Assertions.assertThat(results.get(results.size() - 1).mode()).isEqualTo(mode);

    double actualRate = results.get(results.size() - 1).rate();
    if (mode == Mode.NORMAL) {
      Assertions.assertThat(actualRate).isEqualTo(rate, Offset.offset(0.1));
    } else {
      Assertions.assertThat(actualRate).isEqualTo(rate);
    }
    return this;
  }
}
